Описание регистров в SystemVerilog
Перед тем, как описывать память, необходимо научиться описывать отдельные регистры. Регистр — это базовая ячейка памяти, позволяющая хранить состояние, пока на схему подается питание. В современной электронике, регистр чаще всего строится на D-триггерах. В лабораторной работе по АЛУ уже вскользь упоминалось, что как для описания проводов, так и для описания регистров, используется тип logic
.
logic reg_name;
У регистра может быть несколько входов и один выход. Основных входов, без которых не может существовать регистр два: вход данных и вход тактирующего синхроимпульса. На рисунке они обозначены как D
и clk
. Опциональный вход сигнала сброса (rst
) позволяет обнулять содержимое регистра вне зависимости от входных данных и может работать как с тактовым синхроимпульсом (синхронный сброс), так и без него (асинхронный сброс).
Помимо прочего у регистра также может быть входной сигнал разрешения записи (enable
), который определяет будут ли записаны данные с входного сигнала данных в регистр или нет, опциональный вход установки (set
), позволяющий принудительно выставить значение регистра в единицу.
Выход у регистра один. На рисунке выше он обозначен как Q
.
Важно понимать, что названия приведенных портов не являются чем-то высеченным на камне, они просто описывают функциональное назначение. В процессе описания работы регистра вы будете оперировать только над именем регистра, и сигналами, которые подводите к нему.
Поскольку все сигналы в цифровой схеме передаются по цепям, удобно представлять, что к выходу регистра всегда неявно подключен провод, с именем, совпадающим с именем регистра, поэтому вы можете использовать имя регистра в дальнейшей цифровой логике:
Итак, мы добавили регистр на холст схемы, но как соединить его с какой-то логикой? Предположим, у нас есть сигнал тактового синхроимпульса и данные, которые мы хотим записать:
Данной схеме соответствует код:
module reg_example(
input logic clk,
input logic data,
output logic reg_data
);
logic reg_name;
endmodule
Очевидно, мы хотим подключить сигнал clk
ко входу тактирующего сигнала регистра, вход data
ко входу данных, а выход регистра к выходу reg_data
:
Запись в регистр возможна только по фронту тактирующего синхроимпульса. Фронт — это переход сигнала из нуля в единицу (положительный фронт), либо из единицы в ноль (отрицательный фронт).
Описание регистра, а также указание фронта и тактирующего сигнала происходит в конструкции always_ff
:
always @(posedge clk)
Далее, внутри данной конструкции необходимо указать, что происходит с содержимым регистра. В нашем случае, происходит запись с входного сигнала data
always @(posedge clk) begin
reg_name <= data;
end
Обратите внимание на оператор <=
. В данном случае, это не знак "меньше либо равно", а оператор неблокирующего присваивания. Существует оператор блокирующего присваивания (=
), который меняет способ построения схемы для такого же выражения справа от оператора, однако в данный момент этот оператор останется за рамками курса. Хоть это и плохая практика в обучении, но пока вам надо просто запомнить, что при описании записи в регистр всегда используйте оператор неблокирующего присваивания <=
.
Помимо прочего, нам необходимо связать выход схемы с выходом регистра. Это можно сделать уже известным вам оператором непрерывного присваивания assign
.
Таким образом, итоговый код описания данной схемы примет вид:
module reg_example(
input logic clk,
input logic data,
output logic reg_data
);
logic reg_name;
always @(posedge clk) begin
reg_name <= data;
end
assign reg_data = reg_name;
endmodule
Предположим, мы хотим добавить управление записью в регистр через сигналы enable
и reset
. Это, например, можно сделать следующим образом:
module reg_example(
input logic clk,
input logic data,
input logic reset,
input logic enable,
output logic reg_data
);
logic reg_name;
always_ff @(posedge clk) begin
if(reset) begin
reg_name <= 1'b0;
end
else if(enable) begin
reg_name <= data;
end
end
assign reg_data = reg_name;
endmodule
Обратите внимание на очередность условий. В первую очередь, мы проверяем условие сброса, и только после этого условие разрешения на запись.
Если сперва проверить разрешение на запись, а затем в блоке else
описать логику сброса, то регистр не будет сбрасываться в случае, если enable
будет равен 1
(запись в регистр будет приоритетней его сброса). Если сброс описать не в блоке else
, а в отдельном блоке if
, то может возникнуть неопределенное состояние: нельзя однозначно сказать в какой момент придет сигнал reset
относительно сигнала enable
и что в итоге запишется в регистр. Поэтому при наличии сигнала сброса, остальная логика по записи в регистр должна размещаться в блоке else
.
Кроме того, САПР-ы смотрят на паттерн описания элемента схемы, и когда распознают его, реализуют элемент так как задумывал разработчик. Поэтому при описании регистра всегда сперва описывается сигнал сброса (если он используется) и только затем в блоке else
описывается вся остальная часть логики записи.
Итоговая схема регистра со сбросом и сигналом разрешения записи:
Помимо прочего есть еще одно важное правило, которое необходимо знать при описании регистра:
Присваивание регистру может выполняться только в одном блоке always
Даже если вдруг САПР не выдаст сразу сообщение об ошибке, в конечном итоге, на этапе синтеза схемы она рано или поздно появится в виде сообщения связанного с "multiple drivers".
В блоке присваивания регистру можно описывать и комбинационную логику, стоящую перед ним, например схему:
можно описать как
module reg_example(
input logic clk,
input logic A,
input logic B,
input logic reset,
input logic enable,
output logic reg_data
);
logic reg_name;
always_ff @(posedge clk) begin
if(reset) begin
reg_name <= 1'b0;
end
else if(enable) begin
reg_name <= A & B;
end
end
assign reg_data = reg_name;
endmodule
Однако это всего лишь упрощение. Если вы умеете описывать регистр с подключением к нему всего одного провода на входе данных, вы все равно сможете описать эту схему:
module reg_example(
input logic clk,
input logic A,
input logic B,
input logic reset,
input logic enable,
output logic reg_data
);
logic reg_name; // Обратите внимание, что несмотря на то, что
logic ab; // и reg_name и ab объявлены типом logic,
// ab станет проводом, а reg_name - регистром
// (из-за непрерывного присваивания на ab, и блока
// always_ff для reg_name)
assign ab = A & B;
always_ff @(posedge clk) begin
if(reset) begin
reg_name <= 1'b0;
end
else if(enable) begin
reg_name <= ab;
end
end
assign reg_data = reg_name;
endmodule
Поэтому так важно разобраться в базовом способе описания регистра.
Более того, с точки зрения синтезатора данное описание проще для синтеза, т.к. ему не разделять из одного always
блока комбинационную и синхронные части.
Вообще говоря, регистр в общем смысле этого слова представляет собой многоразрядную конструкцию (в рассмотренном ранее примере, однобитный регистр мог представлять из себя простой D-триггер). Создание многоразрядного регистра мало отличается от создания многоразрядного провода, а описание логики записи в многоразрядный регистр ничем не отличается от логики записи в одноразрядный регистр:
module reg_example(
input logic clk,
input logic [7:0] data,
output logic [7:0] reg_data
);
logic [7:0] reg_name;
always_ff @(posedge clk) begin
reg_name <= data;
end
assign reg_data = reg_name;
endmodule
Итоги главы
- Регистр — это базовая ячейка памяти, позволяющая хранить состояние, пока на схему подается питание.
- Для объявления регистра используется тип
logic
, при необходимости после типа указывается разрядность будущего регистра. - Для описания логики записи в регистр используется блок
always_ff
, в круглых скобках которого указывается тактирующий сигнал и фронт, по которому будет вестись запись, а также (в случае асинхронного сброса), сигнал сброса. - Регистр может иметь различные управляющие сигналы: установки/сброса/разрешения на запись. Логика этих управляющих сигналов является частью логики записи в этот регистр и так же описывается в блоке
always_ff
. - При описании логики записи в регистр, необходимо пользоваться оператором неблокирующего присваивания
<=
. - Нельзя описывать логику записи в регистр более чем в одном блоке
always
(иными словами, операция присваивания для каждого регистра может находиться только в одном блоке always).
Проверь себя
Как, по-вашему, описать на языке SystemVerilog схему, приведённую ниже?